View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   *
18   * $Id: TicketTTLEvictionAlgorithm.java,v 1.10 2005/12/14 14:52:24 catoolsen Exp $
19   */
20  
21  package no.feide.moria.store;
22  
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.LinkedList;
26  import java.util.ListIterator;
27  
28  import no.feide.moria.log.MessageLogger;
29  import no.feide.moria.store.TicketTTLEvictionPolicy.RegionValue;
30  
31  import org.jboss.cache.Fqn;
32  import org.jboss.cache.eviction.EvictedEventNode;
33  import org.jboss.cache.eviction.EvictionAlgorithm;
34  import org.jboss.cache.eviction.EvictionException;
35  import org.jboss.cache.eviction.EvictionPolicy;
36  import org.jboss.cache.eviction.Region;
37  
38  /* TODO: Switch to java.util.concurrent.* classes instead.
39   * See http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
40   * for more information.
41   */
42  import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
43  import EDU.oswego.cs.dl.util.concurrent.Sync;
44  import EDU.oswego.cs.dl.util.concurrent.SyncList;
45  import EDU.oswego.cs.dl.util.concurrent.SyncMap;
46  import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
47  
48  /***
49   * This eviction algorithm expires cache elements after a fixed period, aka Time To Live.
50   *
51   * @author Bjørn Ola Smievoll <b.o.smievoll@conduct.no>
52   * @version $Revision: 1.10 $
53   */
54  public final class TicketTTLEvictionAlgorithm implements EvictionAlgorithm {
55  
56      /*** The logger used by this class. */
57      private final MessageLogger messageLogger = new MessageLogger(TicketTTLEvictionAlgorithm.class);
58  
59      /*** Synchronized list of nodes. */
60      private SyncList nodeList;
61  
62      /*** Synchronized hash map of nodes. */
63      private SyncMap nodeMap;
64  
65      /***
66       * Constructs a new instance.
67       */
68      public TicketTTLEvictionAlgorithm() {
69          super();
70          nodeList = new SyncList(new LinkedList(), new ReentrantWriterPreferenceReadWriteLock());
71          nodeMap = new SyncMap(new HashMap(), new WriterPreferenceReadWriteLock());
72      }
73  
74      /***
75       * Perfoms the eviction algorithm. Called periodically.
76       * @see org.jboss.cache.eviction.EvictionAlgorithm#process(org.jboss.cache.eviction.Region)
77       */
78      public void process(final Region region)
79              throws EvictionException {
80  
81          while (true) {
82  
83              EvictedEventNode node = region.takeLastEventNode();
84  
85              /* Exit if queue is empty. */
86              if (node == null)
87                  break;
88  
89              Fqn fqn = node.getFqn();
90  
91              // TODO This isn't quite right.  If I have a node it must contain a fqn?
92              /* Exit if queue is empty. */
93              if (fqn == null)
94                  break;
95  
96              Integer event = node.getEvent();
97  
98              /* Something's fishy, but we'll just ignore this node and keep on truckin. */
99              if (event == null) {
100                 messageLogger.logWarn("Last event is null for FQN: " + fqn);
101                 continue;
102             } else if (event.equals(EvictedEventNode.ADD_EVENT)) {
103                 processAddedNodes(region, fqn);
104                 messageLogger.logDebug("Got add event");
105             } else if (event.equals(EvictedEventNode.REMOVE_EVENT)) {
106                 processRemovedNodes(fqn);
107                 messageLogger.logDebug("Got remove event");
108             } else if (event.equals(EvictedEventNode.VISIT_EVENT)) {
109                 /* Node visits are of no interest. */
110                 messageLogger.logDebug("Ignoring visit event");
111             } else {
112                 messageLogger.logCritical("Illegal event type: " + event);
113                 continue;
114             }
115         }
116 
117         /* Do the evicto-motion. */
118         prune(region);
119     }
120 
121     public void resetEvictionQueue(final Region region) {
122         // TODO Maybe implement?
123     }
124 
125     /***
126      * Adds nodes to eviction data structures.
127      * @param region Region of tree.
128      * @param fqn    Fully qualified name.
129      */
130     private void processAddedNodes(final Region region, final Fqn fqn) {
131         TicketTTLEvictionPolicy policy = (TicketTTLEvictionPolicy) region.getEvictionPolicy();
132         RegionValue regionValue = policy.getRegionValue(region.getFqn());
133 
134         if (regionValue == null) {
135             messageLogger.logWarn("Values for region not found. Aborting further processing.");
136             return;
137         }
138 
139         /* Now pluss TTL for region gives the time when the ticket should be evicted. */
140         Long nodeEvictionTime = new Long(new Date().getTime() + regionValue.getTimeToLive());
141         NodeEntry node = new NodeEntry(nodeEvictionTime, fqn);
142 
143         nodeList.add(node);
144         nodeMap.put(fqn, node);
145     }
146 
147     /***
148      * Removes nodes from eviction data structures.
149      * @param fqn Fully qualified name.
150      */
151     private void processRemovedNodes(final Fqn fqn) {
152         NodeEntry node = (NodeEntry) nodeMap.remove(fqn);
153         nodeList.remove(node);
154     }
155 
156     /***
157      * Prunes a region of the tree.
158      * @param region Region of tree.
159      * @throws  EvictionException
160      *            If eviction is interrupted or fails.
161      */
162     private void prune(final Region region)
163             throws EvictionException {
164         /* Don't need the time each iteration. Rocket science this ain't. */
165         long now = new Date().getTime();
166 
167         EvictionPolicy policy = region.getEvictionPolicy();
168 
169         Sync lock = nodeList.writerSync();
170 
171         try {
172             lock.acquire();
173             int counter = 0;
174             int nodeListSize = nodeList.size();
175 
176             for (ListIterator i = nodeList.listIterator(); i.hasNext();) {
177                 NodeEntry node = (NodeEntry) i.next();
178 
179                 if (now > node.evictionTime.longValue()) {
180                     nodeMap.remove(node.fqn);
181                     i.remove();
182                     counter++;
183 
184                     try {
185                         policy.evict(node.fqn);
186                         messageLogger.logDebug("Evicted node " + node.fqn);
187                     } catch (Exception e) {
188                         messageLogger.logWarn("Eviction failed for FQN: " + node.fqn, e);
189                         throw new EvictionException("Eviction failed for fqn: " + node.fqn, e);
190                     }
191                 } else {
192                     break;
193                 }
194             }
195 
196             messageLogger.logDebug("Number of nodes in region " + region.getFqn() + ": " + nodeListSize
197                                    + ". Number of evicted nodes: " + counter);
198         } catch (InterruptedException ie) {
199             throw new EvictionException("Node list pruning interrupted", ie);
200         } finally {
201             lock.release();
202         }
203     }
204 
205     /***
206      * Represents a cache node.
207      */
208     private static class NodeEntry {
209 
210         /*** Eviction time. */
211         private final Long evictionTime;
212 
213         /*** Fully qualified name. */
214         private final Fqn fqn;
215 
216         /***
217          * Constructs a new instance.
218          *
219          * @param evictionTime Eviction time.
220          * @param fqn          Fully qualified name.
221          */
222         public NodeEntry(final Long evictionTime, final Fqn fqn) {
223             this.evictionTime = evictionTime;
224             this.fqn = fqn;
225         }
226     }
227 }